package cc.shacocloud.redis.study.util.redis;

import cc.shacocloud.redis.study.util.env.YamlConfigurer;
import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.util.SafeEncoder;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * redis 操作工具
 */
@Slf4j
public class RedisUtil {

    private static JedisPool pool;

    /**
     * redis 函数最大执行时间
     */
    private static final long maxExecutionTime = 1000L;

    @NotNull
    @Contract(" -> new")
    private static JedisPool init() {
        synchronized (RedisUtil.class) {
            YamlConfigurer yamlConfigurer = YamlConfigurer.getDefault();

            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            // 最大连接数
            jedisPoolConfig.setMaxTotal(
                    yamlConfigurer.getIntProperty("redis.max-total",
                            (Runtime.getRuntime().availableProcessors() * 2) + 1)
            );
            // 最大空闲数
            jedisPoolConfig.setMaxIdle(
                    yamlConfigurer.getIntProperty("redis.max-idle", Runtime.getRuntime().availableProcessors())
            );
            // 最小空闲数
            jedisPoolConfig.setMinIdle(
                    yamlConfigurer.getIntProperty("redis.min-idle", 1)
            );

            // 驱逐策略类名
            jedisPoolConfig.setEvictionPolicyClassName(
                    yamlConfigurer.getStrProperty("redis.eviction-policy-class-name",
                            "org.apache.commons.pool2.impl.DefaultEvictionPolicy")
            );
            // 每次驱逐数量
            jedisPoolConfig.setNumTestsPerEvictionRun(
                    yamlConfigurer.getIntProperty("redis.num-tests-per-eviction-run", 3)
            );
            // 当连接池资源用尽后，调用者的最大等待时间
            jedisPoolConfig.setMaxWaitMillis(
                    yamlConfigurer.getLongProperty("redis.max-wait-millis", 3000L)
            );
            // 是否进行空闲测试
            jedisPoolConfig.setTestWhileIdle(
                    yamlConfigurer.getBooleanProperty("redis.test-while-idle", false)
            );
            // 向连接池借用连接时是否进行连接有效性检测，无效连接会被移出，每次借用多执行一次ping命令
            jedisPoolConfig.setTestOnBorrow(
                    yamlConfigurer.getBooleanProperty("redis.test-on-borrow", false)
            );
            // 向连接池归还连接时是否进行连接有效性检测，无效连接会被移出，每次归还多执行一次ping命令
            jedisPoolConfig.setTestOnReturn(
                    yamlConfigurer.getBooleanProperty("redis.test-on-return", false)
            );
            // 当连接池用尽后，调用者是否等待，这个参数是和maxWaitMillis对应的，只有当此参数为true时，maxWaitMillis才会生效
            jedisPoolConfig.setBlockWhenExhausted(
                    yamlConfigurer.getBooleanProperty("redis.block-when-exhausted", true)
            );

            String host = yamlConfigurer.getStrProperty("redis.host", "127.0.0.1");
            int port = yamlConfigurer.getIntProperty("redis.port", 6379);
            String password = yamlConfigurer.getStrProperty("redis.password", null);
            int database = yamlConfigurer.getIntProperty("redis.database", 0);
            int timeout = yamlConfigurer.getIntProperty("redis.timeout", Protocol.DEFAULT_TIMEOUT);

            JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password, database);
            if (log.isInfoEnabled()) log.info("redis连接池初始化参数成功，host：{}，port：{}，database：{}", host, port, database);
            return jedisPool;
        }
    }

    private RedisUtil() {
    }


    /**
     * 初始化Redis 并且执行一次Ping命令，确认服务器运行正常
     */
    public static void initAndPing() throws JedisInitException {
        try {
            String pong = ping();
            if (!"PONG".equals(pong)) throw new JedisConnectionException("redis ping命令返回异常，请检查：" + pong);
        } catch (JedisException e) {
            throw new JedisInitException("初始化jedis时发生例外！", e);
        }

    }

    /**
     * 向 Redis 服务器发送一个 PING ，如果服务器运作正常的话，会返回一个 PONG 。
     * 通常用于测试与服务器的连接是否仍然生效，或者用于测量延迟值。
     */
    public static String ping() {
        return tryFun(BinaryJedis::ping, "ping");
    }

    /**
     * 执行lua脚本
     *
     * @param script 脚本命令
     * @return 执行结果
     */
    public static Object eval(String script) {
        return tryFun(jedis -> jedis.eval(script), "eval");
    }

    /**
     * 执行lua脚本
     *
     * @param script   脚本命令
     * @param keyCount KEYS的数量
     * @param params   参数（包含 KEYS 和 ARGV，KEYS在前 ARGV在后）
     * @return 执行结果
     */
    public static Object eval(String script, int keyCount, Object... params) {
        return eval(BuilderFactoryExpand.COMMON, script, keyCount, RedisParamsFormatUtil.paramsFormat(params));
    }

    /**
     * 执行lua脚本
     *
     * @param script   脚本命令
     * @param keyCount KEYS的数量
     * @param params   参数（包含 KEYS 和 ARGV，KEYS在前 ARGV在后）
     * @return 执行结果
     */
    public static <T> T eval(Builder<T> resultBuilder, String script, int keyCount, Object... params) {
        return eval(resultBuilder, script, keyCount, RedisParamsFormatUtil.paramsFormat(params));
    }

    /**
     * 执行lua脚本
     *
     * @param script   脚本命令
     * @param keyCount KEYS的数量
     * @param params   参数（包含 KEYS 和 ARGV，KEYS在前 ARGV在后）
     * @return 执行结果
     */
    public static Object eval(String script, int keyCount, byte[]... params) {
        return eval(BuilderFactoryExpand.COMMON, script, keyCount, params);
    }


    /**
     * 执行lua脚本
     *
     * @param resultBuilder 结果构建器
     * @param script        脚本命令
     * @param keyCount      KEYS的数量
     * @param params        参数（包含 KEYS 和 ARGV，KEYS在前 ARGV在后）
     * @return 执行结果
     */
    public static <T> T eval(Builder<T> resultBuilder, String script, int keyCount, byte[]... params) {
        return tryFun(jedis -> {
            Object result = jedis.eval(SafeEncoder.encode(script), SafeEncoder.encode(String.valueOf(keyCount)), params);
            return resultBuilder.build(result);
        }, "eval");
    }

    /**
     * 清空当前数据库中的所有 key
     */
    public static void flushDB() {
        tryFun(BinaryJedis::flushDB, "flushDB");
    }


    /**
     * 获取指定键的值。 如果键不存在，则返回null。
     * 如果存储在key处的值不是字符串，则返回错误，因为GET仅能处理字符串值。
     *
     * @param key 键
     * @return 如果键不存在，则返回null。 {@linkplain Jedis#get(String)}
     */
    public static String get(String key) {
        return tryFun(jedis -> jedis.get(key), "get");
    }

    /**
     * 将字符串值设置为键的值。 字符串不能超过1073741824字节（1 GB）。
     *
     * @param key   键
     * @param value 值
     * @return {@linkplain Jedis#set(String, String)}
     */
    public static String set(String key, String value) {
        return tryFun(jedis -> jedis.set(key, value), "set");
    }

    /**
     * 删除指定的键。 如果给定的键不存在，则不会对此键执行任何操作。 该命令返回已删除键的数量。
     *
     * @param keys 键
     * @return 整数回复：如果删除一个或多个键，则大于0的整数，如果不存在指定的键，则返回0
     * {@linkplain Jedis#del(String...)}
     */
    public static Long del(String... keys) {
        return tryFun(jedis -> jedis.del(keys), "del");
    }

    /**
     * 如果键已经存在并且是字符串，则此命令将提供的值附加在字符串的末尾。
     * 如果密钥不存在，则会创建密钥并将其设置为空字符串，因此在这种特殊情况下，APPEND与SET非常相似。
     *
     * @param key   键
     * @param value 追加的字符串
     * @return 整数回复：是追加操作后字符串的总长度。 {@linkplain Jedis#append(String, String)}
     */
    public static Long append(String key, String value) {
        return tryFun(jedis -> jedis.append(key, value), "append");
    }

    /**
     * 测试指定的密钥是否存在。
     * 如果密钥存在，则命令返回true，否则返回false。
     * 请注意，即使将键设置为空字符串也将返回true。
     *
     * @param key 键
     * @return 布尔值回复，如果键存在，则为true，否则为false。 {@linkplain Jedis#exists(String)}
     */
    public static Boolean exists(String key) {
        return tryFun(jedis -> jedis.exists(key), "exists");
    }

    /**
     * 如果key不存在 则设置值
     *
     * @param key   键
     * @param value 值
     * @return 整数回复：如果设置了键，则为1；如果未设置键，则为0 {@linkplain Jedis#setnx(String, String)}
     */
    public static Long setnx(String key, String value) {
        return tryFun(jedis -> jedis.setnx(key, value), "setnx");
    }

    /**
     * 设置key value并指定这个键值的有效期
     *
     * @param key     键
     * @param seconds 有效期 /秒
     * @param value   值
     * @return 状态码回复 {@linkplain Jedis#setex(String, int, String)}
     */
    public static String setex(String key, int seconds, String value) {
        return tryFun(jedis -> jedis.setex(key, seconds, value), "setex");
    }

    /**
     * 用指定的字符串覆盖给定 key 所储存的字符串值，覆盖的位置从偏移量 offset 开始。
     *
     * @param key    键
     * @param offset 偏移量
     * @param value  值
     * @return 被修改后的字符串长度。 {@linkplain Jedis#setrange(String, long, String)}
     */
    public static Long setrange(String key, long offset, String value) {
        return tryFun(jedis -> jedis.setrange(key, offset, value), "setrange");
    }

    /**
     * 获取所有指定键的值。
     * 如果一个或多个键不存在或不是String类型，则返回“ nil”值而不是指定键的值，但是操作永远不会失败。
     *
     * @param keys 键 可以一个也可以多个
     * @return 指定键的值。 {@linkplain Jedis#mget(String...)}
     */
    public static List<String> mget(String... keys) {
        return tryFun(jedis -> jedis.mget(keys), "mget");
    }

    /**
     * 将各个键设置为各个值。 将用新的值替换旧的值，
     *
     * @param keysValues 键值
     * @return 状态代码回复基本上 +OK，因为MSET不会失败 {@linkplain Jedis#mset(String...)}
     */
    public static String mset(String... keysValues) {
        return tryFun(jedis -> jedis.mset(keysValues), "mset");
    }

    /**
     * 将各个键设置为各个值。
     * <p>
     * 给定 key 都不存在时，同时设置一个或多个 key-value 对。如果有一个存在则回滚
     *
     * @param keysValues 键值
     * @return 整数回复：1如果所有键均已设置，则0如果未设置键（已存在至少一个键）
     * {@linkplain Jedis#msetnx(String...)}
     */
    public static Long msetnx(String... keysValues) {
        return tryFun(jedis -> jedis.msetnx(keysValues), "msetnx");
    }

    /**
     * GETSET是原子设置此值并返回旧值命令。
     * 将key设置为字符串值，并返回存储在key处的旧值。
     * 字符串不能超过1073741824字节（1 GB）。
     *
     * @param key   键
     * @param value 值
     * @return 旧值 {@linkplain Jedis#getSet(String, String)}
     */
    public static String getSet(String key, String value) {
        return tryFun(jedis -> jedis.getSet(key, value), "getSet");
    }

    /**
     * 截取得到的子字符串。
     *
     * @param key         键
     * @param startOffset 开始偏移量
     * @param endOffset   结束偏移量
     * @return 子字符串 {@linkplain Jedis#getrange(String, long, long)}
     */
    public static String getrange(String key, long startOffset, long endOffset) {
        return tryFun(jedis -> jedis.getrange(key, startOffset, endOffset), "getrange");
    }

    /**
     * 将键处存储的数字加一。 如果键不存在或包含错误类型的值，请在执行减量操作之前将键设置为值“0”。
     * <p>
     * INCR命令限于64位有符号整数。
     * <p>
     * 注意：这实际上是一个字符串操作，也就是说，在Redis中没有“整数”类型。
     * 只是简单地将存储在密钥处的字符串解析为以10为基数的64位带符号整数，然后将其递增，然后转换回字符串。
     *
     * @param key 键
     * @return 返回递增后的值 {@linkplain Jedis#incr(String)}
     */
    public static Long incr(String key) {
        return tryFun(jedis -> jedis.incr(key), "incr");
    }

    /**
     * 工作方式类似于 {@linkplain #incr(String)}，但要增加指定的整数
     *
     * @param key       键
     * @param increment 增量
     * @return 返回递增后的值 {@linkplain Jedis#incrBy(String, long)}
     */
    public static Long incrBy(String key, long increment) {
        return tryFun(jedis -> jedis.incrBy(key, increment), "incrBy");
    }

    /**
     * 将键处存储的数字减一。 如果键不存在或包含错误类型的值，请在执行减量操作之前将键设置为值“0”。
     * <p>
     * INCR命令限于64位有符号整数。
     * <p>
     * 注意：这实际上是一个字符串操作，也就是说，在Redis中没有“整数”类型。
     * 只是简单地将存储在密钥处的字符串解析为以10为基数的64位带符号整数，然后将其递增，然后转换回字符串。
     *
     * @param key 键
     * @return 返回递减后的值 {@linkplain Jedis#decr(String)}
     */
    public static Long decr(String key) {
        return tryFun(jedis -> jedis.decr(key), "decr");
    }

    /**
     * 工作方式与{@linkplain #decr(String)}相似，但是减少指定的整数。
     * <p>
     * INCR命令限于64位有符号整数。
     * <p>
     * 注意：这实际上是一个字符串操作，也就是说，在Redis中没有“整数”类型。
     * 只是简单地将存储在密钥处的字符串解析为以10为基数的64位带符号整数，然后将其递增，然后转换回字符串。
     *
     * @param key       键
     * @param decrement 递减的值
     * @return 返回递减后的值 {@linkplain Jedis#decrBy(String, long)}
     */
    public static Long decrBy(String key, long decrement) {
        return tryFun(jedis -> jedis.decrBy(key, decrement), "decrBy");
    }

    /**
     * 获取字符串值的长度。 当 key 不存在时，返回 0
     *
     * @param key 键
     * @return 字符串值的长度 {@linkplain Jedis#strlen(String)}
     */
    public static Long strLen(String key) {
        return tryFun(jedis -> jedis.strlen(key), "strLen");
    }

    /**
     * 如果该字段不存在，则将指定的哈希字段设置为指定的值。
     *
     * @param key   键
     * @param field 属性
     * @param value 值
     * @return 如果该字段已经存在，则返回0，否则，如果创建新字段，则返回1。
     * {@linkplain Jedis#hsetnx(String, String, String)}
     */
    public static Long hsetnx(String key, String field, String value) {
        return tryFun(jedis -> jedis.hsetnx(key, field, value), "hsetnx");
    }

    /**
     * 将指定的哈希字段设置为指定的值。
     * <p>
     * 如果密钥不存在，则会创建一个包含哈希的新密钥。
     *
     * @param key   键
     * @param field 属性
     * @param value 值
     * @return 如果该字段已经存在，并且HSET刚刚生成了该值的更新，则返回0，否则，如果创建了新字段，则返回1。
     * {@linkplain Jedis#hset(String, String, String)}
     */
    public static Long hset(String key, String field, String value) {
        return hset(key, field, SafeEncoder.encode(value));
    }

    /**
     * 将指定的哈希字段设置为指定的值。
     * <p>
     * 如果密钥不存在，则会创建一个包含哈希的新密钥。
     *
     * @param key   键
     * @param field 属性
     * @param value 值
     * @return 如果该字段已经存在，并且HSET刚刚生成了该值的更新，则返回0，否则，如果创建了新字段，则返回1。
     * {@linkplain Jedis#hset(String, String, String)}
     */
    public static Long hset(String key, String field, byte[] value) {
        return tryFun(jedis -> jedis.hset(SafeEncoder.encode(key), SafeEncoder.encode(field), value), "hset");
    }


    /**
     * 将各个字段设置为各个值。 HMSET用新值替换旧值。
     * <p>
     * 如果密钥不存在，则会创建一个包含哈希的新密钥。
     *
     * @param key  键
     * @param hash hash值
     * @return 返回OK 或 如果哈希为空，则异常 {@linkplain Jedis#hmset(String, Map)}
     */
    public static String hmset(String key, Map<String, String> hash) {
        return tryFun(jedis -> jedis.hmset(key, hash), "hmset");
    }

    /**
     * 如果key包含哈希，则检索与指定字段关联的值。
     * <p>
     * 如果找不到该字段或该键不存在，则返回一个特殊的“ nil”值。
     *
     * @param key    键
     * @param failed 字段
     * @return 返回给定字段的值。如果给定的字段或 key 不存在时，返回 nil 。
     * {@linkplain Jedis#hget(String, String)}
     */
    public static String hget(String key, String failed) {
        return tryFun(jedis -> jedis.hget(key, failed), "hget");
    }

    /**
     * 在指定键上设置超时。 超时后，服务器将自动删除密钥。
     * 在Redis术语中，具有关联超时的键被认为是易变的。
     * <p>
     * 易变键像其他键一样存储在磁盘上，超时也像数据集的所有其他方面一样持久。
     * 保存包含到期的数据集并停止服务器不会停止时间流，因为Redis在磁盘上存储密钥的时间不再是Unix时间，而是剩余的秒数。
     * <p>
     * 从Redis 2.1.3开始，您可以更新已经设置了过期设置的键的超时值。
     * 也可以使用{@linkplain Jedis#persist(String)}命令将密钥变成普通密钥来撤消过期。
     *
     * @param key     键
     * @param seconds 秒
     * @return 整数回复，具体是：1：设置了超时。
     * 0：未设置超时，因为密钥已经具有关联的超时（仅在Redis版本<2.1.3，Redis> = 2.1.3中会愉快地更新超时），或者密钥不存在。
     * {@linkplain Jedis#expire(String, int)}
     */
    public static Long expire(String key, int seconds) {
        return tryFun(jedis -> jedis.expire(key, seconds), "expire");
    }

    /**
     * 检索与指定字段关联的值。
     * <p>
     * 如果某些指定的字段不存在，则返回nil值。 不存在的密钥被视为空哈希。
     *
     * @param key    键
     * @param fields 可以是一个 也可以是 String数组
     * @return 多批量答复专门以与请求相同的顺序列出与指定字段关联的所有值的列表。
     * {@linkplain Jedis#hmget(String, String...)}
     */
    public List<String> hmget(String key, String... fields) {
        return tryFun(jedis -> jedis.hmget(key, fields), "hmget");
    }

    /**
     * 按值递增存储在键处散列中字段中存储的数字。
     * 如果密钥不存在，则会创建一个包含哈希的新密钥。
     * 如果字段不存在或不包含字符串，则在应用操作之前将值设置为0。
     * 由于value参数是带符号的，因此您可以使用此命令执行增量和减量。
     * <p>
     * HINCRBY支持的值范围限制为64位带符号整数。
     *
     * @param key   键
     * @param field 字段
     * @param value 增量值
     * @return 整数回复增量操作后，字段中的新值。 {@linkplain Jedis#hincrBy(String, String, long)}
     */
    public Long hincrBy(String key, String field, long value) {
        return tryFun(jedis -> jedis.hincrBy(key, field, value), "hincrBy");
    }

    /**
     * 测试哈希中是否存在指定字段
     *
     * @param key   键
     * @param field 字段
     * @return 如果存储在key处的哈希值包含指定字段，则返回true。 如果未找到密钥或字段不存在，则返回false。
     * {@linkplain Jedis#hexists(String, String)}
     */
    public Boolean hexists(String key, String field) {
        return tryFun(jedis -> jedis.hexists(key, field), "hexists");
    }

    /**
     * 返回哈希中的项目数。
     *
     * @param key 键
     * @return 密钥中存储的哈希中包含的条目（字段）数。 如果指定的键不存在，则假定为空哈希，则返回0。
     * {@linkplain Jedis#hlen(String)}
     */
    public Long hlen(String key) {
        return tryFun(jedis -> jedis.hlen(key), "hlen");
    }

    /**
     * 从存储在key处的哈希中删除指定的字段。
     *
     * @param key    键
     * @param fields 可以是一个 也可以是 一个数组
     * @return 如果该字段存在于哈希中，则将其删除并返回1，否则返回0，并且不执行任何操作。
     * {@linkplain Jedis#hkeys(String)}
     */
    public Long hdel(String key, String... fields) {
        return tryFun(jedis -> jedis.hdel(key, fields), "hdel");
    }

    /**
     * 返回哈希中的所有字段。
     *
     * @param key 键
     * @return 包含在哈希中的所有字段名称 {@linkplain Jedis#hkeys(String)}
     */
    public static Set<String> hkeys(String key) {
        return tryFun(jedis -> jedis.hkeys(key), "hkeys");
    }

    /**
     * 在哈希中返回所有值。
     *
     * @param key 键
     * @return 包含在哈希中的所有字段值。 {@linkplain Jedis#hvals(String)}
     */
    public static List<String> hvals(String key) {
        return tryFun(jedis -> jedis.hvals(key), "hvals");
    }

    /**
     * 返回哈希中的所有字段和关联值。
     *
     * @param key 键
     * @return 包含在哈希中的所有字段和值。 {@linkplain Jedis#hgetAll(String)}
     */
    public static Map<String, String> hgetAll(String key) {
        return tryFun(jedis -> jedis.hgetAll(key), "hgetAll");
    }

    /**
     * 将字符串值添加到存储在键处的列表的开头（LPUSH）。
     * 如果键不存在，则会在追加操作之前创建一个空列表。
     * 如果键存在但不是列表，则返回错误。
     *
     * @param key  键
     * @param strs 可以是一个string 也可以是string数组
     * @return 返回list的value个数 {@linkplain Jedis#lpush(String, String...)}
     */
    public static Long lpush(String key, String... strs) {
        return tryFun(jedis -> jedis.lpush(key, strs), "lpush");
    }

    /**
     * 将字符串值添加到存储在键处的列表的结尾（RPUSH）。
     * 如果键不存在，则会在追加操作之前创建一个空列表。
     * 如果键存在但不是列表，则返回错误。
     *
     * @param key  键
     * @param strs 可以是一个string 也可以是string数组
     * @return 返回list的value个数 {@linkplain Jedis#rpush(String, String...)}
     */
    public static Long rpush(String key, String... strs) {
        return tryFun(jedis -> jedis.rpush(key, strs), "rpush");
    }

    /**
     * 将值 value 插入到列表 key 当中，位于值 pivot 之前或之后。
     *
     * @param key   键
     * @param where {@linkplain ListPosition}
     * @param pivot list里面的value
     * @param value 添加的value
     * @return {@linkplain Jedis#linsert(String, ListPosition, String, String)}
     */
    public static Long linsert(String key, ListPosition where, String pivot, String value) {
        return tryFun(jedis -> jedis.linsert(key, where, pivot, value), "linsert");
    }

    /**
     * 将一个新值设置为键处List的索引位置处的元素。
     * <p>
     * 超出范围的索引将生成错误。
     * <p>
     * 与其他接受索引的列表命令类似，索引可以是负数
     * 从列表末尾开始的元素。 因此，-1是最后一个元素，-2是倒数第二个，依此类推。
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return 成功返回OK {@linkplain Jedis#lset(String, long, String)}
     */
    public static String lset(String key, long index, String value) {
        return tryFun(jedis -> jedis.lset(key, index, value), "lset");
    }

    /**
     * 从列表中删除值元素的第一次计数。 如果count为零，则删除所有元素。
     * 如果count是负数，则从尾部到头部删除元素，而不是从头到尾去掉，这是正常的行为。
     * 因此，例如，具有count -2和hello作为要从列表中删除的值（a，b，c，hello，x，hello，hello）的LREM将剥离列表（a，b，c，hello，x）。
     * 删除的元素数以整数形式返回，有关返回值的更多信息，请参见下文。
     * 请注意，LREM将不存在的键视为空列表，因此针对不存在的键的LREM将始终返回0。
     *
     * @param key   键
     * @param count 当count为0时删除全部
     * @param value 值
     * @return 返回被删除的个数 {@linkplain Jedis#lrem(String, long, String)}
     */
    public static Long lrem(String key, long count, String value) {
        return tryFun(jedis -> jedis.lrem(key, count, value), "lrem");
    }

    /**
     * 修剪现有列表，使其仅包含指定范围的指定元素。
     * 开始和结束是从零开始的索引。 0是列表的第一个元素（列表头），1是下一个元素，依此类推。
     * <p>
     * 例如，LTRIM foobar 0 2将修改存储在foobar键处的列表，以便仅保留列表的前三个元素。
     * <p>
     * start和stop也可以是负数，指示与列表末尾的偏移量。 例如，-1是列表的最后一个元素，-2是倒数第二个元素，依此类推。
     * <p>
     * 索引超出范围不会产生错误：如果start在列表的末尾，或者start> end，则保留一个空列表作为值。
     * 如果结束于列表的末尾，则Redis将像列表的最后一个元素一样使用它。
     *
     * @return 成功返回OK {@linkplain Jedis#ltrim(String, long, long)}
     */
    public static String ltrim(String key, long start, long stop) {
        return tryFun(jedis -> jedis.ltrim(key, start, stop), "ltrim");
    }

    /**
     * 以原子方式返回并删除列表的第一个（LPOP）元素。
     * 例如，如果列表包含元素“ a”，“ b”，“ c”，则LPOP将返回“ a”，并且列表将变为“ b”，“ c”。
     * <p>
     * 如果键不存在或列表已经为空，则返回特殊值“ nil”。
     *
     * @param key 键
     * @return {@linkplain Jedis#lpop(String)}
     */
    public static String lpop(String key) {
        return tryFun(jedis -> jedis.lpop(key), "lpop");
    }

    /**
     * 以原子方式返回并删除列表的最后一个（RPOP）元素。
     * 例如，如果列表包含元素“ a”，“ b”，“ c”，则RPOP将返回“ c”，并且列表将变为“ a”，“ b”。
     * <p>
     * 如果键不存在或列表已经为空，则返回特殊值“ nil”。
     *
     * @param key 键
     * @return {@linkplain Jedis#rpop(String)}
     */
    public static String rpop(String key) {
        return tryFun(jedis -> jedis.rpop(key), "rpop");
    }

    /**
     * 以原子方式返回并删除srckey列表的最后一个（尾）元素，并将该元素推入dstkey列表的第一个（头）元素。
     * 例如，如果源列表包含元素“ a”，“ b”，“ c”，而目标列表包含元素“ foo”，“ bar”，
     * 则在RPOPLPUSH命令之后，两个列表的内容将为“ a”， “ b”和“ c”，“ foo”，“ bar”。
     * <p>
     * 如果键不存在或列表已经为空，则返回特殊值“ nil”。
     * 如果srckey和dstkey相同，则该操作等效于从列表中删除最后一个元素并将其作为列表的第一个元素推送，因此它是“列表轮换”命令。
     *
     * @return {@linkplain Jedis#rpoplpush(String, String)}
     */
    public static String rpoplpush(String srckey, String dstkey) {
        return tryFun(jedis -> jedis.rpoplpush(srckey, dstkey), "rpoplpush");
    }

    /**
     * 返回存储在指定键处的列表的指定元素。 0是第一个元素，1是第二个元素，依此类推。
     * 支持负索引，例如-1是最后一个元素，-2是倒数第二个，依此类推。
     * <p>
     * 如果存储在key处的值不是列表类型，则返回错误。 如果索引超出范围，则返回“ nil”答复。
     *
     * @param key   键
     * @param index 索引
     * @return {@linkplain Jedis#lindex(String, long)}
     */
    public static String lindex(String key, long index) {
        return tryFun(jedis -> jedis.lindex(key, index), "lindex");
    }

    /**
     * 返回存储在指定键处的列表的长度。
     * 如果键不存在，则返回零（与空列表相同的行为）。
     * 如果存储在key处的值不是列表，则返回错误。
     *
     * @param key 键
     * @return {@linkplain Jedis#llen(String)}
     */
    public static Long llen(String key) {
        return tryFun(jedis -> jedis.llen(key), "llen");
    }

    /**
     * 返回存储在指定键处的列表的指定元素。 开始和结束是从零开始的索引。
     * 0是列表的第一个元素（列表头），1是下一个元素，依此类推。
     * <p>
     * 例如，LRANGE foobar 0 2将返回列表的前三个元素。
     * <p>
     * 如果start 为 0 stop 为 -1 则返回全部的list中的value
     *
     * @return {@linkplain Jedis#lrange(String, long, long)}
     */
    public static List<String> lrange(String key, long start, long stop) {
        return tryFun(jedis -> jedis.lrange(key, start, stop), "lrange");
    }

    /**
     * 将指定的成员添加到存储在key的设置值中。 如果member已经是集合的成员，则不执行任何操作。
     * 如果key不存在，则创建一个具有指定成员作为唯一成员的新集合。
     * 如果键存在但不保存设置值，则返回错误。
     *
     * @param key     键
     * @param members 可以是一个String 也可以是一个String数组
     * @return 添加成功的个数 {@linkplain Jedis#sadd(String, String...)}
     */
    public static Long sadd(String key, String... members) {
        return tryFun(jedis -> jedis.sadd(key, members), "sadd");
    }

    /**
     * 从key中存储的设置值中删除指定的成员。如果member不是set的成员，则不执行任何操作。如果键没有设置值，则返回错误。
     *
     * @param key     键
     * @param members 可以是一个String 也可以是一个String数组
     * @return 删除的个数 {@linkplain Jedis#srem(String, String...)}
     */
    public static Long srem(String key, String... members) {
        return tryFun(jedis -> jedis.srem(key, members), "srem");
    }

    /**
     * 从Set中删除一个随机元素，将其作为返回值返回。如果Set为空或键不存在，则返回nil对象。
     *
     * @param key 键
     * @return {@linkplain Jedis#spop(String)}
     */
    public static String spop(String key) {
        return tryFun(jedis -> jedis.spop(key), "spop");
    }

    /**
     * 通过key获取set中的差集，以第一个set为标准
     *
     * @param keys 可以是一个string 则返回set中所有的value 也可以是string数组
     * @return {@linkplain Jedis#sdiff(String...)}
     */
    public static Set<String> sdiff(String... keys) {
        return tryFun(jedis -> jedis.sdiff(keys), "sdiff");
    }

    /**
     * 此命令的工作方式与{@linkplain #sdiff(String...)}完全相同，但不是返回，而是将结果集存储在dstkey中。
     *
     * @param dstkey 差集存入的key
     * @param keys   可以是一个string 则返回set中所有的value 也可以是string数组
     * @return {@linkplain Jedis#sdiffstore(String, String...)}
     */
    public static Long sdiffstore(String dstkey, String... keys) {
        return tryFun(jedis -> jedis.sdiffstore(dstkey, keys), "sdiffstore");
    }

    /**
     * 通过key返回所有set的交集
     * 像在{@linkplain #lrange(String, long, long)}中一样，结果作为多批回复发送到客户端（有关更多信息，请参见协议规范）。
     * 如果仅指定了一个密钥，则此命令产生的结果与{@linkplain #smembers(String)} 相同。
     *
     * @param keys 可以是一个string 也可以是一个string数组
     * @return {@linkplain Jedis#sinter(String...)}
     */
    public static Set<String> sinter(String... keys) {
        return tryFun(jedis -> jedis.sinter(keys), "sinter");
    }

    /**
     * 此命令的工作方式与{@linkplain #sinter(String...)}完全相同，但不返回结果集，存储为dstkey。
     *
     * @param dstkey 新的数据集
     * @param keys   可以 是一个string 也可以是一个string数组
     * @return {@linkplain Jedis#sinterstore(String, String...)}
     */
    public static Long sinterstore(String dstkey, String... keys) {
        return tryFun(jedis -> jedis.sinterstore(dstkey, keys), "sinterstore");
    }

    /**
     * 通过key返回所有set的并集
     *
     * @param keys 可以 是一个string 也可以是一个string数组
     * @return {@linkplain Jedis#sunion(String...)}
     */
    public static Set<String> sunion(String... keys) {
        return tryFun(jedis -> jedis.sunion(keys), "sunion");
    }

    /**
     * 此命令的工作方式与{@linkplain #sunion(String...)}完全相同，但是没有返回结果集，而是将其存储为dstkey。
     * dstkey中的任何现有值将被覆盖。
     *
     * @param dstkey 新的数据集
     * @param keys   可以是一个string 也可以是一个string数组
     * @return {@linkplain Jedis#sunionstore(String, String...)}
     */
    public static Long sunionstore(String dstkey, String... keys) {
        return tryFun(jedis -> jedis.sunionstore(dstkey, keys), "sunionstore");
    }

    /**
     * 通过key将set中的value移除并添加到第二个set中
     *
     * @param srckey 需要移除的
     * @param dstkey 添加的
     * @param member set中的value
     * @return {@linkplain Jedis#smove(String, String, String)}
     */
    public static Long smove(String srckey, String dstkey, String member) {
        return tryFun(jedis -> jedis.smove(srckey, dstkey, member), "smove");
    }

    /**
     * 通过key获取set中value的个数
     *
     * @return {@linkplain Jedis#scard(String)}
     */
    public static Long scard(String key) {
        return tryFun(jedis -> jedis.scard(key), "scard");
    }

    /**
     * 通过key判断value是否是set中的元素
     *
     * @return {@linkplain Jedis#sismember(String, String)}
     */
    public static Boolean sismember(String key, String member) {
        return tryFun(jedis -> jedis.sismember(key, member), "sismember");
    }

    /**
     * 通过key获取set中随机的value,不删除元素
     *
     * @return {@linkplain Jedis#srandmember(String)}
     */
    public static String srandmember(String key) {
        return tryFun(jedis -> jedis.srandmember(key), "srandmember");
    }

    /**
     * 通过key获取set中所有的value
     *
     * @return {@linkplain Jedis#smembers(String)}
     */
    public static Set<String> smembers(String key) {
        return tryFun(jedis -> jedis.smembers(key), "smembers");
    }


    /**
     * 通过key向zset中添加value,score,其中score就是用来排序的
     * 如果该value已经存在则根据score更新元素
     *
     * @return {@linkplain Jedis#zadd(String, double, String)}
     */
    public static Long zadd(String key, double score, String member) {
        return tryFun(jedis -> jedis.zadd(key, score, member), "zadd");
    }

    /**
     * 通过key删除在zset中指定的value
     *
     * @param key     键
     * @param members 可以是一个string 也可以是一个string数组
     * @return {@linkplain Jedis#zrem(String, String...)}
     */
    public static Long zrem(String key, String... members) {
        return tryFun(jedis -> jedis.zrem(key, members), "zrem");
    }

    /**
     * 通过key增加该zset中value的score的值
     *
     * @return {@linkplain Jedis#zincrby(String, double, String)}
     */
    public static Double zincrby(String key, double score, String member) {
        return tryFun(jedis -> jedis.zincrby(key, score, member), "zincrby");
    }

    /**
     * 通过key返回zset中value的排名
     * 下标从小到大排序
     *
     * @return {@linkplain Jedis#zrank(String, String)}
     */
    public static Long zrank(String key, String member) {
        return tryFun(jedis -> jedis.zrank(key, member), "zrank");
    }

    /**
     * 通过key返回zset中value的排名
     * 下标从大到小排序
     *
     * @return {@linkplain Jedis#zrevrank(String, String)}
     */
    public static Long zrevrank(String key, String member) {
        return tryFun(jedis -> jedis.zrevrank(key, member), "zrevrank");
    }

    /**
     * 通过key将获取score从start到end中zset的value
     * socre从大到小排序
     * 当start为0 end为-1时返回全部
     *
     * @return {@linkplain Jedis#zrevrange(String, long, long)}
     */
    public static Set<String> zrevrange(String key, long start, long end) {
        return tryFun(jedis -> jedis.zrevrange(key, start, end), "zrevrange");
    }

    /**
     * 通过key返回指定score内zset中的value
     *
     * @return {@linkplain Jedis#zrevrangeByScore(String, String, String)}
     */
    public static Set<String> zrevrangeByScore(String key, String max, String min) {
        return tryFun(jedis -> jedis.zrevrangeByScore(key, max, min), "zrevrangeByScore");
    }

    /**
     * 通过key返回指定score内zset中的value
     *
     * @return {@linkplain Jedis#zrevrangeByScore(String, double, double)}
     */
    public static Set<String> zrevrangeByScore(String key, double max, double min) {
        return tryFun(jedis -> jedis.zrevrangeByScore(key, max, min), "zrevrangeByScore");
    }

    /**
     * 返回指定区间内zset中value的数量
     *
     * @return {@linkplain Jedis#zcount(String, String, String)}
     */
    public static Long zcount(String key, String min, String max) {
        return tryFun(jedis -> jedis.zcount(key, min, max), "zcount");
    }

    /**
     * 返回排序后的集合基数（元素数）。如果键不存在，则返回0，类似于空排序集。
     *
     * @return {@linkplain Jedis#zcard(String)}
     */
    public static Long zcard(String key) {
        return tryFun(jedis -> jedis.zcard(key), "zcard");
    }

    /**
     * 返回key排序集中指定元素的分数。如果指定的元素在排序集中不存在，或者键根本不存在，则返回一个特殊的'nil'值。
     *
     * @return {@linkplain Jedis#zscore(String, String)}
     */
    public static Double zscore(String key, String member) {
        return tryFun(jedis -> jedis.zscore(key, member), "zscore");
    }

    /**
     * 删除键在排序集中的所有元素，键在开始和结束之间。起点和终点基于0，
     * 等级0是得分最低的元素。开头和结尾都可以为负，它们表示从具有最高等级的元素开始的偏移量。
     * 示例：-1是得分最高的元素，-2是得分第二高的元素，依此类推。
     *
     * @return {@linkplain Jedis#zremrangeByRank(String, long, long)}
     */
    public static Long zremrangeByRank(String key, long start, long stop) {
        return tryFun(jedis -> jedis.zremrangeByRank(key, start, stop), "zremrangeByRank");
    }

    /**
     * 移除键在排序集的所有元素，且得分介于min和max之间（包括等于最小或最大的元素。
     *
     * @return {@linkplain Jedis#zremrangeByScore(String, double, double)}
     */
    public static Long zremrangeByScore(String key, double min, double max) {
        return tryFun(jedis -> jedis.zremrangeByScore(key, min, max), "zremrangeByScore");
    }

    /**
     * 返回满足pattern表达式的所有key
     * keys(*)
     * 返回所有的key
     *
     * @param pattern 表达式
     * @return {@linkplain Jedis#keys(String)}
     */
    public static Set<String> keys(String pattern) {
        return tryFun(jedis -> jedis.keys(pattern), "keys");
    }

    /**
     * 通过key判断值得类型
     *
     * @param key 键
     * @return {@linkplain Jedis#type(String)}
     */
    public static String type(String key) {
        return tryFun(jedis -> jedis.type(key), "type");
    }

    /**
     * 执行函数，该返回会自动获取{@linkplain Jedis} 对象，并且在方法执行结束时 返还的连接池中
     *
     * @param function 函数
     * @param <R>      返回值泛型
     * @return 函数返回值
     */
    public static <R> R tryFun(@NotNull Function<Jedis, R> function) {
        return tryFun(function, false, "", maxExecutionTime);
    }

    /**
     * 执行函数，该返回会自动获取{@linkplain Jedis} 对象，并且在方法执行结束时 返还的连接池中
     *
     * @param function 函数
     * @param <R>      返回值泛型
     * @return 函数返回值
     */
    public static <R> R tryFun(@NotNull Function<Jedis, R> function, String functionName) {
        return tryFun(function, true, functionName, maxExecutionTime);
    }

    /**
     * 执行函数，该返回会自动获取{@linkplain Jedis} 对象，并且在方法执行结束时 返还的连接池中
     *
     * @param function         函数
     * @param oversee          是否开启监控 true 开启 反之不开启,如为false则后面两个参数无效
     * @param functionName     函数名称
     * @param maxExecutionTime 最大执行时间
     * @param <R>              泛型
     * @return 函数返回值
     */
    public static <R> R tryFun(@NotNull Function<Jedis, R> function, boolean oversee, String functionName,
                               long maxExecutionTime) {
        long s1 = 0;
        long s2 = 0;
        long s3;

        if (oversee) s1 = DateUtil.current(false);

        // 获取Jedis对象
        try (Jedis jedis = getJedis()) {
            if (oversee) s2 = DateUtil.current(false);

            // 执行函数
            final R result = function.apply(jedis);

            if (oversee) {
                s3 = DateUtil.current(false);
                long executionTime = s3 - s1;
                if (executionTime > maxExecutionTime) {
                    String msg = "函数[" + functionName + "]执行时间超出预期";
                    log.warn("{}， jedis获取连接耗时：{}/ms，tryFun处理耗时：{}/ms，总耗时：{}/ms",
                            msg, s2 - s1, s3 - s2, executionTime, new Exception(msg));
                }
            }

            return result;
        }
    }

    /**
     * 获取{@linkplain Jedis}对象
     *
     * @return {@linkplain Jedis}
     */
    public static Jedis getJedis() {
        if (pool == null) {
            synchronized (RedisUtil.class) {
                if (pool == null) {
                    pool = init();
                }
            }
        }
        return pool.getResource();
    }

    /**
     * 关闭连接池
     */
    public static void close() {
        pool.close();
    }
}
